mod decoder;

use super::server::Server;

use std::{
    collections::HashMap,
    io::{prelude::*, BufReader, BufWriter},
    net::{Shutdown, TcpStream},
    thread,
};

pub struct HttpRequest {
    method: String,
    socket_addr: String,
    path: String,
    version: String,
    headers: HashMap<String, String>,
    proxy_headers: HashMap<String, String>,
    reader: BufReader<TcpStream>,
    writer: BufWriter<TcpStream>,
}

pub struct HttpResponse {
    response_line: String,
    headers: HashMap<String, String>,
    reader: BufReader<TcpStream>,
}

const BUF_SIZE: usize = 4096;

pub fn serv() -> Server {
    let mut server = Server::new("0.0.0.0:10090", "http-proxy-thread");

    if let Err(err) = server.start(|stream| {
        if let Err(err) = handle_stream(stream) {
            log::warn!("A error occures when handling stream:: {:?}", err);
        }
    }) {
        log::error!("Error ocures when start server {:?}", err);
    }
    server
}

fn handle_stream(client_stream: TcpStream) -> Result<(), Box<dyn std::error::Error>> {
    let req = decoder::decoder_request(client_stream.try_clone()?)?;
    log::debug!("{:?} {:?} {:?}", req.method, req.path, req.version);
    log::debug!("Headers: {:?}", req.headers);
    log::debug!("Proxy headers: {:?}", req.proxy_headers);
    if req.method == "CONNECT" {
        open_http_tunnel(req)?;
    } else {
        handle_http_request(req)?;
        client_stream.shutdown(Shutdown::Both)?;
    }

    Ok(())
}

fn handle_http_request(mut req: HttpRequest) -> Result<(), Box<dyn std::error::Error>> {
    let mut content_length = if let Some(len) = req.headers.get("Content-Length") {
        len.parse()?
    } else {
        0
    };

    let mut server_stream = TcpStream::connect(req.socket_addr)?;
    let request_line =
        req.method.to_string() + " " + req.path.as_str() + " " + req.version.as_str() + "\r\n";
    server_stream.write(request_line.as_bytes())?;
    for (header, value) in &req.headers {
        server_stream.write((header.clone() + ": " + value + "\r\n").as_bytes())?;
    }
    // TODO not support keep-alive this time.
    server_stream.write("Connection: close\r\n".to_string().as_bytes())?;
    server_stream.write("\r\n".to_string().as_bytes())?;
    // write body to server.
    let mut buf = [0u8; BUF_SIZE];
    loop {
        if content_length == 0 {
            break;
        }
        let size = if BUF_SIZE > content_length {
            content_length
        } else {
            BUF_SIZE
        };

        req.reader.read_exact(&mut buf[..size])?;
        server_stream.write(&mut buf[..size])?;

        content_length -= size;
    }
    server_stream.flush()?;

    log::debug!("Start to read data from server.");

    // read response from server.
    let mut res = decoder::decode_response(server_stream)?;
    req.writer.write(res.response_line.as_bytes())?;
    for (header, value) in &res.headers {
        req.writer
            .write((header.clone() + ": " + value + "\r\n").as_bytes())?;
    }
    req.writer
        .write("Connection: close\r\n".to_string().as_bytes())?;
    req.writer.write("\r\n".to_string().as_bytes())?;
    req.writer.flush()?;
    if let Some(len) = res.headers.get("Content-Length") {
        let mut content_length = len.parse()?;
        loop {
            if content_length == 0 {
                break;
            }
            let size = if BUF_SIZE > content_length {
                content_length
            } else {
                BUF_SIZE
            };

            log::debug!("try to read {:?} bytes from server.", size);

            res.reader.read_exact(&mut buf[..size])?;
            req.writer.write(&mut buf[..size])?;
            req.writer.flush()?;

            content_length -= size;
        }
    } else {
        loop {
            let size = res.reader.read(&mut buf[..])?;
            if size == 0 {
                log::debug!("reader from server end!");
                req.writer.flush()?;
                break;
            }
            req.writer.write(&buf[..size])?;
            req.writer.flush()?;
        }
    };

    log::debug!("REQ write end!");
    req.writer.flush()?;
    Ok(())
}

fn open_http_tunnel(mut req: HttpRequest) -> Result<(), Box<dyn std::error::Error>> {
    let server_stream = TcpStream::connect(req.socket_addr)?;

    let mut server_reader = BufReader::new(server_stream.try_clone()?);
    let server_writer: BufWriter<Box<dyn Write + Send>> = BufWriter::new(Box::new(server_stream));

    thread::spawn(move || {
        send_client_data_to_serever(req.reader, server_writer)
            .expect("Error when sending to client. ");
    });

    let res_lines =
        "HTTP/1.1 200 Connection established\r\nProxy-Agent: rust-proxy\r\n\r\n".to_string();
    req.writer.write(res_lines.as_bytes())?;
    req.writer.flush()?;

    let mut buffer = [0u8; 4096];
    loop {
        let size = server_reader.read(&mut buffer[..])?;
        if size == 0 {
            req.writer.flush()?;
            break;
        }
        req.writer.write(&buffer[..size])?;
        req.writer.flush()?;
    }
    Ok(())
}

fn send_client_data_to_serever(
    mut client_reader: BufReader<TcpStream>,
    mut server_writer: BufWriter<Box<dyn Write + Send>>,
) -> Result<(), Box<dyn std::error::Error>> {
    loop {
        let mut buffer = [0u8; 4096];

        let size = client_reader.read(&mut buffer[..])?;
        if size == 0 {
            server_writer.flush()?;
            break;
        }
        server_writer.write(&buffer[..size])?;
        server_writer.flush()?;
    }
    Ok(())
}
